/*-------------------------------------------------------------------------
 *
 * constraint.c
 *	  PostgreSQL 约束支持代码。
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * IDENTIFICATION
 *	  src/backend/commands/constraint.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "access/genam.h"
#include "access/heapam.h"
#include "access/tableam.h"
#include "catalog/index.h"
#include "commands/trigger.h"
#include "executor/executor.h"
#include "utils/builtins.h"
#include "utils/rel.h"
#include "utils/snapmgr.h"


/*
 * unique_key_recheck - 触发函数以进行延迟唯一性检查。
 *
 * 这现在还进行延迟排除约束检查，因此名称在某种程度上是
 * 历史性的。
 *
 * 这是作为 INSERT 和 UPDATE 的 AFTER ROW 触发器调用的，
 * 针对任何被记录为可能违反可延迟唯一
 * 或排除约束的行。
 *
 * 这可能是一个语句结束检查、提交时检查，或者是由
 * SET CONSTRAINTS 命令触发的检查。
 */
Datum unique_key_recheck(PG_FUNCTION_ARGS)
{
	TriggerData *fc_trigdata = (TriggerData *) fcinfo->context;
	const char *fc_funcname = "unique_key_recheck";
	ItemPointerData fc_checktid;
	ItemPointerData fc_tmptid;
	Relation	fc_indexRel;
	IndexInfo  *fc_indexInfo;
	EState	   *fc_estate;
	ExprContext *fc_econtext;
	TupleTableSlot *fc_slot;
	Datum		fc_values[INDEX_MAX_KEYS];
	bool		fc_isnull[INDEX_MAX_KEYS];

	/*
	 * 确保这是作为 AFTER ROW 触发器被调用。注意：
	 * 可翻译的错误字符串与 ri_triggers.c 共享，因此要抵制
	 * 将函数名称融入其中的诱惑。
	 */
	if (!CALLED_AS_TRIGGER(fcinfo))
		ereport(ERROR,
				(errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
				 errmsg("function \"%s\" was not called by trigger manager",
						fc_funcname)));

	if (!TRIGGER_FIRED_AFTER(fc_trigdata->tg_event) ||
		!TRIGGER_FIRED_FOR_ROW(fc_trigdata->tg_event))
		ereport(ERROR,
				(errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
				 errmsg("function \"%s\" must be fired AFTER ROW",
						fc_funcname)));

	/*
	 * 获取已插入/更新的新数据。
	 */
	if (TRIGGER_FIRED_BY_INSERT(fc_trigdata->tg_event))
		fc_checktid = fc_trigdata->tg_trigslot->tts_tid;
	else if (TRIGGER_FIRED_BY_UPDATE(fc_trigdata->tg_event))
		fc_checktid = fc_trigdata->tg_newslot->tts_tid;
	else
	{
		ereport(ERROR,
				(errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
				 errmsg("function \"%s\" must be fired for INSERT or UPDATE",
						fc_funcname)));
		ItemPointerSetInvalid(&fc_checktid);	/* 保持编译器安静 */
	}

	fc_slot = table_slot_create(fc_trigdata->tg_relation, NULL);

	/*
	 * 如果 checktid 指向的行现在已经死亡（即，在我们的事务中
	 * 已插入然后删除），我们可以跳过检查。但是，我们
	 * 必须小心，因为这个触发器只在响应于索引插入时被排队；
	 * 这意味着它不会在例如 HOT 更新时被排队。我们被调用的行
	 * 现在可能已经死亡，但有一个活动的 HOT 子行，在这种情况下
	 * 我们仍然需要进行检查——
	 * 实际上，我们是针对活动子行应用检查，
	 * 尽管我们可以使用此行的值，因为根据定义，我们感兴趣的所有
	 * 列都是相同的。
	 *
	 * 这看起来可能只是一个优化，因为索引 AM 将在抛出错误之前
	 * 进行相同的测试。但这实际上是为了正确性，因为索引 AM
	 * 如果找不到该行的索引条目也会抛出错误。如果行死掉了，
	 * 那么索引条目也可能已被标记为死，并且甚至
	 * 被移除。
	 */
	fc_tmptid = fc_checktid;
	{
		IndexFetchTableData *fc_scan = table_index_fetch_begin(fc_trigdata->tg_relation);
		bool		fc_call_again = false;

		if (!table_index_fetch_tuple(fc_scan, &fc_tmptid, SnapshotSelf, fc_slot,
									 &fc_call_again, NULL))
		{
			/*
			 * 所有通过索引条目引用的行都是死亡的，因此跳过
			 * 检查。
			 */
			ExecDropSingleTupleTableSlot(fc_slot);
			table_index_fetch_end(fc_scan);
			return PointerGetDatum(NULL);
		}
		table_index_fetch_end(fc_scan);
	}

	/*
	 * 打开索引，获取 RowExclusiveLock，就像我们要更新它一样。
	 * （这防止了索引架构的可能更改，而不是防止并发更新。）
	 */
	fc_indexRel = index_open(fc_trigdata->tg_trigger->tgconstrindid,
						  RowExclusiveLock);
	fc_indexInfo = BuildIndexInfo(fc_indexRel);

	/*
	 * 通常索引不会有表达式，但如果有，我们需要一个
	 * EState 来评估它们。对于排除约束，我们也需要它，
	 * 即使它们只是简单的列。
	 */
	if (fc_indexInfo->ii_Expressions != NIL ||
		fc_indexInfo->ii_ExclusionOps != NULL)
	{
		fc_estate = CreateExecutorState();
		fc_econtext = GetPerTupleExprContext(fc_estate);
		fc_econtext->ecxt_scantuple = fc_slot;
	}
	else
		fc_estate = NULL;

	/*
	 * 为我们需要检查的索引条目形成索引值和 isnull 标志。
	 *
	 * 注意：如果索引使用的函数并不像它们应该是的那样
	 * 不可变，这可能会产生与原始不同的索引元组。
	 * 索引 AM 可以通过验证找到与元组的 TID 匹配的索引条目
	 * 来捕获此类错误。对于排除约束，我们在 check_exclusion_constraint() 
	 * 中检查这一点。
	 */
	FormIndexDatum(fc_indexInfo, fc_slot, fc_estate, fc_values, fc_isnull);

	/*
	 * 现在进行适当的检查。
	 */
	if (fc_indexInfo->ii_ExclusionOps == NULL)
	{
		/*
		 * 注意：这不是一个真实的插入；这是检查已经插入的索引条目是否唯一。通过元组的tid（即未经table_index_fetch_tuple()修改的）是正确的，即使行现在已经死，因为那是索引将知道的TID。
		 */
		index_insert(fc_indexRel, fc_values, fc_isnull, &fc_checktid,
					 fc_trigdata->tg_relation, UNIQUE_CHECK_EXISTING,
					 false, fc_indexInfo);
	}
	else
	{
		/*
		 * 对于排除约束，我们只是进行正常检查，但现在可以抛出错误。在HOT更新情况下，我们必须在这里使用活动的HOT子项的TID，否则check_exclusion_constraint会认为子项存在冲突。
		 */
		check_exclusion_constraint(fc_trigdata->tg_relation, fc_indexRel, fc_indexInfo,
								   &fc_tmptid, fc_values, fc_isnull,
								   fc_estate, false);
	}

	/*
	 * 如果这有效，那么这个索引条目是唯一或未排除的，我们完成了。
	 */
	if (fc_estate != NULL)
		FreeExecutorState(fc_estate);

	ExecDropSingleTupleTableSlot(fc_slot);

	index_close(fc_indexRel, RowExclusiveLock);

	return PointerGetDatum(NULL);
}
